/*
 *  Copyright (C) 2010 Matthias Buch-Kromann <mbk.isv@cbs.dk>
 * 
 *  This file is part of the IncrementalParser package.
 *  
 *  The IncrementalParser program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 * 
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.osdtsystem.incparser.graphs;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import org.osdtsystem.incparser.data.CONLLSentence;
import org.osdtsystem.incparser.data.CONLLToken;
import org.osdtsystem.incparser.features.FeatureHandler;
import org.osdtsystem.incparser.features.FeatureVector;

/**
 *
 * @author Matthias Buch-Kromann <mbk.isv@cbs.dk>
 */
public class ConllAdapter {
    BaseGraph baseGraph;
    final int layer;
    public final int ID, FORM, LEMMA, CPOSTAG, POSTAG, FEATS, HEAD, DEPREL, PHEAD, PDEPREL;
    FeatureHandler types, strings, features;
    CONLLSentence conllSentence;

    public ConllAdapter(BaseGraph baseGraph,
            FeatureHandler edgeTypes, FeatureHandler strings,
            FeatureHandler features, String namePrefix) {
        // Base graph and layer
        this.baseGraph = baseGraph;
        this.layer = baseGraph.addLayer(namePrefix + "dependency tree");

        // Int features
        this.ID = baseGraph.addIntFeature(namePrefix + "ID");
        this.FORM = baseGraph.addIntFeature(namePrefix + "FORM");
        this.LEMMA = baseGraph.addIntFeature(namePrefix + "LEMMA");
        this.CPOSTAG = baseGraph.addIntFeature(namePrefix + "CPOSTAG");
        this.POSTAG = baseGraph.addIntFeature(namePrefix + "POSTAG");
        this.FEATS = baseGraph.addObjectFeature(namePrefix + "FEATS");
        this.HEAD = baseGraph.addIntFeature(namePrefix + "HEAD");
        this.DEPREL = baseGraph.addIntFeature(namePrefix + "DEPREL");
        this.PHEAD = baseGraph.addIntFeature(namePrefix + "PHEAD");
        this.PDEPREL = baseGraph.addIntFeature(namePrefix + "PDEPREL");

        // Feature handlers
        this.types = edgeTypes;
        this.strings = strings;
        this.features = features;
        clear();
    }

    public ConllAdapter(BaseGraph baseGraph,
            FeatureHandler edgeTypes, FeatureHandler strings,
            FeatureHandler features) {
        this(baseGraph, edgeTypes, strings, features, "");
    }

    public int form(Graph graph, int node) {
        return graph.intFeature(node, FORM);
    }

    public int lemma(Graph graph, int node) {
        return graph.intFeature(node, LEMMA);
    }

    public int cpostag(Graph graph, int node) {
        return graph.intFeature(node, CPOSTAG);
    }

    public int postag(Graph graph, int node) {
        return graph.intFeature(node, POSTAG);
    }

    public int[] feats(Graph graph, int node) {
        return (int[]) graph.objectFeature(node, FEATS);
    }

    public int field(Graph graph, int node, int field) {
        return graph.intFeature(node, field);
    }

    public int goldHead(Graph graph, int node) {
        return graph.intFeature(node, HEAD);
    }
    
    public int goldDeprel(Graph graph, int node) {
        return graph.intFeature(node, DEPREL);
    }
    
    public int goldPHead(Graph graph, int node) {
        return graph.intFeature(node, PHEAD);
    }
    
    public int goldPDeprel(Graph graph, int node) {
        return graph.intFeature(node, PDEPREL);
    }

    public int layer() {
        return layer;
    }

    public void clear() {
        baseGraph.clearNodes();
        baseGraph.addNode(this.layer);
    }

    public void clearEdges() {
        baseGraph.clearEdges();
    }

    public void clearGold() {
        for (int node = baseGraph.nodes() - 1; node >= 0; --node) {
            baseGraph.setIntFeature(node, HEAD, 0);
            baseGraph.setIntFeature(node, DEPREL, 0);
            baseGraph.setIntFeature(node, PHEAD, 0);
            baseGraph.setIntFeature(node, PDEPREL, 0);
        }
    }

    public void addEdge(int innode, int outnode, int type) {
        baseGraph.addEdge(layer, innode, outnode, type);
    }

    public int addNode() {
        return baseGraph.addNode(layer);
    }

    public void moveEdge(int innode, int newoutnode, int newtype) {
        LocalGraph localGraph = baseGraph.localGraph(innode, layer);
        int oldoutnode = localGraph.headNode();
        int oldtype = localGraph.headType();
        baseGraph.moveEdge(layer, innode, oldoutnode, oldtype, newoutnode, newtype);
    }

    public void removeEdge(int innode, int outnode, int type) {
        baseGraph.removeEdge(layer, innode, outnode, type);
    }

    public void removeEdges() {
        baseGraph.removeEdges(layer);
    }

    public int intFeature(Graph graph, int node, int feature) {
        return graph.intFeature(node, feature);
    }

    public int intFeatures() {
        return baseGraph.intFeatures();
    }

    public LocalGraph localGraph(Graph graph, int node) {
        return graph.localGraph(node, layer);
    }

    public int nodes(Graph graph) {
        return graph.nodes();
    }

    public Object objectFeature(Graph graph, int node, int feature) {
        return graph.objectFeature(node, feature);
    }

    public int objectFeatures() {
        return baseGraph.objectFeatures();
    }

    public void setIntFeature(Graph graph, int node, int feature, int value) {
        graph.setIntFeature(node, feature, value);
    }

    public void setObjectFeature(Graph graph, int node, int feature, Object value) {
        graph.setObjectFeature(node, feature, value);
    }

    public void trimToSize(Graph graph) {
        graph.trimToSize();
    }

    public int typeID(String type) {
        return types.insertFeature(type, type);
    }

    public int stringID(String string) {
        return strings.feature(string);
    }

    public int featureID(Object feature) {
        return features.feature(feature);
    }

    public BaseGraph baseGraph() {
        return baseGraph;
    }

    public double score() {
        return baseGraph.score();
    }

    public double score(Graph graph) {
        return graph.score();
    }

    public FeatureVector featureVector() {
        return baseGraph.featureVector();
    }

    public FeatureVector featureVector(Graph graph) {
        return graph.featureVector();
    }


    public double localScore(Graph graph, int node) {
        return graph.localScore(node);
    }

    public void writeConllGraph(Writer writer, Graph graph) {
        if (conllSentence == null)
            throw new UnsupportedOperationException("No conll sentence present");
        try {
            for (int node = 1; node < nodes(graph); ++node) {
                CONLLToken token = conllSentence.get(node -1);
                LocalGraph localGraph = graph.localGraph(node, layer);
                int head = localGraph.headNode();
                int type = localGraph.headType();
                String deprel = types.featureName(type);
                writer.write(token.toString(head, deprel));
                writer.append("\n");
            }
            writer.append("\n");
        } catch (IOException ex) {
            throw new RuntimeException("Unhandled IO exception", ex);
        }
    }



    public String toString(Graph graph) {
        StringWriter s = new StringWriter();
        writeConllGraph(s, graph);
        return s.toString();
    }

    public void setConllSentence(CONLLSentence conllSentence) {
        this.conllSentence = conllSentence;
    }

    public CONLLSentence conllSentence() {
        return conllSentence;
    }

    public boolean dominates(Graph graph, int dominator, int node) {
        return dominates(graph, dominator, node, 0);
    }

    public boolean dominates(Graph graph, int dominator, int node, int height) {
        if (node == 0)
            return false;
        if (height > nodes(graph))
            return false; // Trapped in a cycle
        if (node == dominator)
            return height > 0;
        return dominates(graph, dominator, localGraph(graph, node).headNode(), height + 1);
    }

}
